// 
//   Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
//  
//   This program is free software: you can redistribute it and/or modify
//   it under the terms of the GNU Affero General Public License as
//   published by the Free Software Foundation, either version 3 of the
//   License, or (at your option) any later version.
//  
//   This program is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU Affero General Public License for more details.
//  
//   You should have received a copy of the GNU Affero General Public License
//   along with this program.  If not, see <http://www.gnu.org/licenses/>.
//  
//  

using System;
using System.Linq;
using System.Threading;
using System.Collections;
using System.Collections.Generic;

using Anculus.Core;
using Galaxium.Protocol.Xmpp.Library.Xml;
using Galaxium.Protocol.Xmpp.Library.Core;

// TODO: PEP storage

namespace Galaxium.Protocol.Xmpp.Library.Extensions
{
	public class Bookmarks: IAttachable, IEnumerable<ConferenceBookmark>
	{
		private Dictionary<JabberID, ConferenceBookmark> _bookmarks;
		
		public event EventHandler Updated;
		
		public event EventHandler<JidEventArgs> Added;
		public event EventHandler<JidEventArgs> Removed;
		public event EventHandler<JidEventArgs> Changed;
		
		public bool IsSupported { get; private set; }
		
		public Client Client { get; private set; }
		
		private bool _received;
		
		private object _lock = new Object ();
		
		public Bookmarks ()
		{
			_bookmarks = new Dictionary<JabberID, ConferenceBookmark> ();
		}

		void HandleConnectionStateChanged (object sender, ConnectionStateEventArgs e)
		{
			if (e.State == ConnectionState.Connected) {
				Request ();
			}
			
			if (e.State == ConnectionState.Disconnected) {
				_received = false;
				IsSupported = false;
			}
		}
		
		private void Update (List<ConferenceBookmark> new_bookmarks)
		{
			var old_bookmarks = new List<ConferenceBookmark> (_bookmarks.Values);
			
			foreach (var removed in old_bookmarks.Except (new_bookmarks)) {
				if (_received) {
					OnRemoved (removed.UID);
					_bookmarks.Remove (removed.UID);
				}
			}
				
			foreach (var added in new_bookmarks.Except (old_bookmarks)) {
				_bookmarks.Add (added.UID, added);
				OnAdded (added.UID);
			}
			
			foreach (var common in new_bookmarks.Intersect (old_bookmarks)) {
				var old = _bookmarks [common.UID];
				if (old.Name != common.Name ||
				    old.Nickname != common.Nickname ||
				    old.Password != common.Password ||
				    old.AutoJoin != common.AutoJoin) {
					
					_bookmarks [common.UID] = common;
					OnChanged (common.UID);
				}
			}
			
			if (!_received)
				Send ();
			
			_received = true;
		}
		
		public void Request ()
		{
			ThreadPool.QueueUserWorkItem ((o) => {
				try {
					var result = XmlStorage.RetrieveData (Client, "storage", Namespaces.Bookmarks, -1);
					IsSupported = true;
					
					var new_bookmarks = new List<ConferenceBookmark> ();
				
					foreach (var elm in result.EachChild ("conference"))
						new_bookmarks.Add (new ConferenceBookmark (elm));
				
					lock (_lock) { Update (new_bookmarks); }
				}
				catch (Exception e) {
					Log.Error (e, "Error when retrieving bookmarks.");
					IsSupported = false;
					OnUpdated ();
				}
			});
		}
		
		private void Send ()
		{
			if (!IsSupported || Client.ConnectionState != ConnectionState.Connected) return;
			
			ThreadPool.QueueUserWorkItem ((o) => {
				try {
					lock (_lock) {
						var elm = new Element ("storage", Namespaces.Bookmarks);
						foreach (var bookmark in _bookmarks.Values)
							elm.AppendChild (bookmark.ToXml ());
						XmlStorage.StoreData (Client, elm, -1);
					}
				}
				catch (Exception e) {
					Log.Error (e, "Error when sending bookmarks. Disabling.");
					IsSupported = false;
				}
			});
		}
		
		public void SetBookmark (JabberID uid, string name, string nick, string pass, bool auto)
		{
			ThreadPool.QueueUserWorkItem ((o) => {
				bool exists;
				lock (_lock) {
					exists = _bookmarks.ContainsKey (uid);
					_bookmarks [uid] = new ConferenceBookmark (uid, name, nick, pass, auto);
				}
				Send ();
				if (exists)
					OnChanged (uid);
				else
					OnAdded (uid);
			});
		}
		
		public void UnsetBookmark (JabberID uid)
		{
			ThreadPool.QueueUserWorkItem ((o) => {
				lock (_lock) {
					if (!_bookmarks.ContainsKey (uid)) return;
					_bookmarks.Remove (uid);
				}
				Send ();
				OnRemoved (uid);
			});
		}
		
		public ConferenceBookmark GetBookmark (JabberID uid)
		{
			return _bookmarks [uid];
		}
		
		private void OnUpdated ()
		{
			if (Updated != null)
				Updated (this, EventArgs.Empty);
		}
		
		private void OnAdded (JabberID uid)
		{
			if (Added != null)
				Added (this, new JidEventArgs (uid));
		}
		
		private void OnRemoved (JabberID uid)
		{
			if (Removed != null)
				Removed (this, new JidEventArgs (uid));
		}
		
		private void OnChanged (JabberID uid)
		{
			if (Changed != null)
				Changed (this, new JidEventArgs (uid));
		}
		
		IEnumerator IEnumerable.GetEnumerator ()
		{
			return GetEnumerator ();
		}
		
		public IEnumerator<ConferenceBookmark> GetEnumerator ()
		{
			return _bookmarks.Values.GetEnumerator ();
		}
		
		public void Attach (Client client)
		{
			if (Client != null)
				throw new InvalidOperationException ("Already attached");
			
			Client = client;
			Client.ConnectionStateChanged += HandleConnectionStateChanged;
		}
		
		public void Detach ()
		{
			if (Client == null) return;
			Client.ConnectionStateChanged -= HandleConnectionStateChanged;
			Client = null;
			_received = false;
			IsSupported = false;
			_bookmarks = new Dictionary<JabberID, ConferenceBookmark> ();
		}
	}
}
